package drds.binlog.binlog_event.event;

import drds.binlog.binlog_event.CharSet;
import drds.binlog.binlog_event.LogByteBlock;
import drds.binlog.binlog_event.LogEvent;
import drds.binlog.common.Authors;
import drds.common.Author;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;

import java.io.IOException;
import java.nio.charset.Charset;

/**
 * A Query_log_event is created for each query that modifies the database,
 * unless the query is logged row-based. The Post-Header has five components:
 * <table>
 * <caption>Post-Header for Query_log_event</caption>
 * <tr>
 * <th>Name</th>
 * <th>Format</th>
 * <th>Description</th>
 * </tr>
 * <tr>
 * <td>slave_proxy_id</td>
 * <td>4 byte unsigned integer</td>
 * <td>An integer identifying the client thread that issued the query. The id is
 * unique per server. (Note, however, that two threads on different servers may
 * have the same slave_proxy_id.) This is used createDateTimeLong a client thread creates a
 * temporary table local to the client. The slave_proxy_id is used to
 * distinguish temporary tables that belong to different clients.</td>
 * </tr>
 * <tr>
 * <td>exec_time</td>
 * <td>4 byte unsigned integer</td>
 * <td>The time from createDateTimeLong the query started to createDateTimeLong it was logged in the binlog,
 * in seconds.</td>
 * </tr>
 * <tr>
 * <td>db_len</td>
 * <td>1 byte integer</td>
 * <td>The length of the name of the currently selected database.</td>
 * </tr>
 * <tr>
 * <td>error_code</td>
 * <td>2 byte unsigned integer</td>
 * <td>Error code generated by the master. If the master fails, the slave will
 * fail with the same error code, except for the error codes ER_DB_CREATE_EXISTS
 * == 1007 and ER_DB_DROP_EXISTS == 1008.</td>
 * </tr>
 * <tr>
 * <td>status_vars_len</td>
 * <td>2 byte unsigned integer</td>
 * <td>The length of the status_vars block of the Body, in bytes. See
 * query_log_event_status_vars "below".</td>
 * </tr>
 * </table>
 * The Body has the following components:
 * <table>
 * <caption>Body for Query_log_event</caption>
 * <tr>
 * <th>Name</th>
 * <th>Format</th>
 * <th>Description</th>
 * </tr>
 * <tr>
 * <td>query_log_event_status_vars status_vars</td>
 * <td>status_vars_len bytes</td>
 * <td>Zero or more status variables. Each status variable consists of one byte
 * identifying the variable stored, followed by the value of the variable. The
 * possible variables are listed separately in the table
 * Table_query_log_event_status_vars "below". MySQL always writes events in the
 * order defined below; however, it is capable of reading them in any order.</td>
 * </tr>
 * <tr>
 * <td>db</td>
 * <td>db_len+1</td>
 * <td>The currently selected database, as a null-terminated string. (The
 * trailing zero is redundant since the length is already known; it is db_len
 * from Post-Header.)</td>
 * </tr>
 * <tr>
 * <td>query</td>
 * <td>variable length string without trailing zero, extending to the end of the
 * event (determined by the length field of the Common-Header)</td>
 * <td>The SQL query.</td>
 * </tr>
 * </table>
 * The following table lists the status variables that may appear in the
 * status_vars field. Table_query_log_event_status_vars
 * <table>
 * <caption>Status variables for Query_log_event</caption>
 * <tr>
 * <th>Status variable</th>
 * <th>1 byte identifier</th>
 * <th>Format</th>
 * <th>Description</th>
 * </tr>
 * <tr>
 * <td>flags2</td>
 * <td>Q_FLAGS2_CODE == 0</td>
 * <td>4 byte bitfield</td>
 * <td>The flags in thd->options, binary AND-ed with OPTIONS_WRITTEN_TO_BIN_LOG.
 * The thd->options bitfield contains options for "SELECT". OPTIONS_WRITTEN
 * identifies those options that need to be written to the binlog (not all do).
 * Specifically, OPTIONS_WRITTEN_TO_BIN_LOG equals (OPTION_AUTO_IS_NULL |
 * OPTION_NO_FOREIGN_KEY_CHECKS | OPTION_RELAXED_UNIQUE_CHECKS |
 * OPTION_NOT_AUTOCOMMIT), or 0x0c084000 in hex. These flags correspond to the
 * SQL variables SQL_AUTO_IS_NULL, FOREIGN_KEY_CHECKS, UNIQUE_CHECKS, and
 * AUTOCOMMIT, documented in the "SET Syntax" section of the MySQL Manual. This
 * field is always written to the binlog in version >= 5.0, and never written in
 * version < 5.0.</td>
 * </tr>
 * <tr>
 * <td>sql_mode</td>
 * <td>Q_SQL_MODE_CODE == 1</td>
 * <td>8 byte bitfield</td>
 * <td>The sql_mode variable. See the section "SQL Modes" in the MySQL manual,
 * and see mysql_priv.h for a list of the possible flags. Currently
 * (2007-10-04), the following flags are available:
 * <p>
 * <pre>
 *     MODE_REAL_AS_FLOAT==0x1
 *     MODE_PIPES_AS_CONCAT==0x2
 *     MODE_ANSI_QUOTES==0x4
 *     MODE_IGNORE_SPACE==0x8
 *     MODE_NOT_USED==0x10
 *     MODE_ONLY_FULL_GROUP_BY==0x20
 *     MODE_NO_UNSIGNED_SUBTRACTION==0x40
 *     MODE_NO_DIR_IN_CREATE==0x80
 *     MODE_POSTGRESQL==0x100
 *     MODE_ORACLE==0x200
 *     MODE_MSSQL==0x400
 *     MODE_DB2==0x800
 *     MODE_MAXDB==0x1000
 *     MODE_NO_KEY_OPTIONS==0x2000
 *     MODE_NO_TABLE_OPTIONS==0x4000
 *     MODE_NO_FIELD_OPTIONS==0x8000
 *     MODE_MYSQL323==0x10000
 *     MODE_MYSQL323==0x20000
 *     MODE_MYSQL40==0x40000
 *     MODE_ANSI==0x80000
 *     MODE_NO_AUTO_VALUE_ON_ZERO==0x100000
 *     MODE_NO_BACKSLASH_ESCAPES==0x200000
 *     MODE_STRICT_TRANS_TABLES==0x400000
 *     MODE_STRICT_ALL_TABLES==0x800000
 *     MODE_NO_ZERO_IN_DATE==0x1000000
 *     MODE_NO_ZERO_DATE==0x2000000
 *     MODE_INVALID_DATES==0x4000000
 *     MODE_ERROR_FOR_DIVISION_BY_ZERO==0x8000000
 *     MODE_TRADITIONAL==0x10000000
 *     MODE_NO_AUTO_CREATE_USER==0x20000000
 *     MODE_HIGH_NOT_PRECEDENCE==0x40000000
 *     MODE_PAD_CHAR_TO_FULL_LENGTH==0x80000000
 * </pre>
 * <p>
 * All these flags are replicated from the server. However, all flags except
 * MODE_NO_DIR_IN_CREATE are honored by the slave; the slave always preserves
 * its old value of MODE_NO_DIR_IN_CREATE. For a rationale, see comment in
 * Query_log_event::do_apply_event in log_event.cc. This field is always written
 * to the binlog.</td>
 * </tr>
 * <tr>
 * <td>catalog</td>
 * <td>Q_CATALOG_NZ_CODE == 6</td>
 * <td>Variable-length string: the length in bytes (1 byte) followed by the
 * characters (at most 255 bytes)</td>
 * <td>Stores the client's current catalog. Every database belongs to a catalog,
 * the same way that every table belongs to a database. Currently, there is only
 * one catalog, "std". This field is written if the length of the catalog is >
 * 0; otherwise it is not written.</td>
 * </tr>
 * <tr>
 * <td>auto_increment</td>
 * <td>Q_AUTO_INCREMENT == 3</td>
 * <td>two 2 byte unsigned integers, totally 2+2=4 bytes</td>
 * <td>The two variables auto_increment_increment and auto_increment_offset, in
 * that order. For more information, see "System variables" in the MySQL manual.
 * This field is written if auto_increment > 1. Otherwise, it is not written.</td>
 * </tr>
 * <tr>
 * <td>connectionCharset</td>
 * <td>Q_CHARSET_CODE == 4</td>
 * <td>three 2 byte unsigned integers, totally 2+2+2=6 bytes</td>
 * <td>The three variables character_set_client, collation_connection, and
 * collation_server, in that order. character_set_client is a code identifying
 * the character set and collation used by the client to encode the query.
 * collation_connection identifies the character set and collation that the
 * master converts the query to createDateTimeLong it receives it; this is useful createDateTimeLong
 * comparing literal strings. collation_server is the default character set and
 * collation used createDateTimeLong a new database is created. See also
 * "Connection Character Sets and Collations" in the MySQL 5.1 manual. All three
 * variables are codes identifying a (character set, collation) pair. To see
 * which codes map to which pairs, run the query "SELECT id, character_set_name,
 * collation_name FROM COLLATIONS". Cf. Q_CHARSET_DATABASE_CODE below. This
 * field is always written.</td>
 * </tr>
 * <tr>
 * <td>time_zone</td>
 * <td>Q_TIME_ZONE_CODE == 5</td>
 * <td>Variable-length string: the length in bytes (1 byte) followed by the
 * characters (at most 255 bytes).
 * <td>The time_zone of the master. See also "System Variables" and
 * "MySQL Server Time Zone Support" in the MySQL manual. This field is written
 * if the length of the time zone string is > 0; otherwise, it is not written.</td>
 * </tr>
 * <tr>
 * <td>lc_time_names_number</td>
 * <td>Q_LC_TIME_NAMES_CODE == 7</td>
 * <td>2 byte integer</td>
 * <td>A code identifying a table of month and day names. The mapping from codes
 * to languages is defined in sql_locale.cc. This field is written if it is not
 * 0, i.e., if the locale is not en_US.</td>
 * </tr>
 * <tr>
 * <td>charset_database_number</td>
 * <td>Q_CHARSET_DATABASE_CODE == 8</td>
 * <td>2 byte integer</td>
 * <td>The value of the collation_database system variable (in the source code
 * stored in thd->variables.collation_database), which holds the code for a
 * (character set, collation) pair as described above (see Q_CHARSET_CODE).
 * collation_database was used in old versions (???WHEN). Its value was loaded
 * createDateTimeLong issuing a "use db" query and could be changed by issuing a
 * "SET collation_database=xxx" query. It used to affect the "LOAD DATA INFILE"
 * and "CREATE TABLE" commands. In newer versions, "CREATE TABLE" has been
 * changed to take the character set from the database of the created table,
 * rather than the character set of the current database. This makes a
 * difference createDateTimeLong creating a table in another database than the current one.
 * "LOAD DATA INFILE" has not yet changed to do this, but there are plans to
 * eventually do it, and to make collation_database read-only. This field is
 * written if it is not 0.</td>
 * </tr>
 * <tr>
 * <td>table_map_for_update</td>
 * <td>Q_TABLE_MAP_FOR_UPDATE_CODE == 9</td>
 * <td>8 byte integer</td>
 * <td>The value of the table map that is to be updated by the multi-table
 * update query statement. Every bit of this variable represents a table, and is
 * set to 1 if the corresponding table is to be updated by this statement. The
 * value of this variable is set createDateTimeLong executing a multi-table update statement
 * and used by slave to apply event_filter rules without opening all the tables on
 * slave. This is required because some tables may not exist on slave because of
 * the event_filter rules.</td>
 * </tr>
 * </table>
 * Query_log_event_notes_on_previous_versions Notes on Previous Versions Status
 * vars were introduced in version 5.0. To read earlier versions correctly,
 * check the length of the Post-Header. The status variable Q_CATALOG_CODE == 2
 * existed in MySQL 5.0.x, where 0<=x<=3. It was identical to Q_CATALOG_CODE,
 * except that the string had a trailing '\0'. The '\0' was removed in 5.0.4
 * since it was redundant (the string length is stored before the string). The
 * Q_CATALOG_CODE will never be written by a new master, but can still be
 * understood by a new slave. See Q_CHARSET_DATABASE_CODE in the table above.
 * When adding new status vars, please don't forget to update the
 * MAX_SIZE_LOG_EVENT_STATUS, and update function code_name
 *
 * @see mysql-5.1.6/sql/logevent.cc - Query_log_event
 */
@Author(name = Authors.LI_YANG)
@Slf4j
public class QueryLogEvent extends LogEvent
{

    /**
     * the maximum number of updated databases that a status of query-log-event
     * can carry. it can redefined within a range [1..
     * over_max_dbs_in_event_mts].
     */
    public static final int max_dbs_in_event_mts = 16;

    /**
     * createDateTimeLong the actual number of databases exceeds max_dbs_in_event_mts the
     * value of over_max_dbs_in_event_mts is is put into the mts_accessed_dbs
     * status.
     */
    public static final int over_max_dbs_in_event_mts = 254;

    public static final int system_charset_mbmaxlen = 3;
    public static final int name_char_len = 64;
    /* field/table name length */
    public static final int name_len = (name_char_len * system_charset_mbmaxlen);

    /**
     * max number of possible extra bytes in a replication event compared to a
     * packet (i.e. a query) sent from client to master; first, an auxiliary
     * log_event status vars estimation:
     */
    public static final int max_size_log_event_status = (1 + 4 /* logType, flags2 */
            + 1 + 8 /*
                                                                  * logType,
                                                                  * sql_mode
                                                                  */
            + 1 + 1 + 255 /*
                                                                        * logType,
                                                                        * length
                                                                        * ,
                                                                        * catalog
                                                                        */
            + 1 + 4 /*
                                                                  * logType,
                                                                  * auto_increment
                                                                  */
            + 1 + 6 /*
                                                                  * logType,
                                                                  * connectionCharset
                                                                  */
            + 1 + 1 + 255 /*
                                                                        * logType,
                                                                        * length
                                                                        * ,
                                                                        * time_zone
                                                                        */
            + 1 + 2 /*
                                                                  * logType,
                                                                  * lc_time_names_number
                                                                  */
            + 1 + 2 /*
                                                                  * logType,
                                                                  * charset_database_number
                                                                  */
            + 1 + 8 /*
                                                                  * logType,
                                                                  * table_map_for_update
                                                                  */
            + 1 + 4 /*
                                                                  * logType,
                                                                  * master_data_written
                                                                  */
                                                         /*
                                                          * logType, db_1, db_2,
                                                          * ...
                                                          */
                                                         /* logType, microseconds */
                                                         /*
                                                          * mariadb logType,
                                                          * sec_part of now()
                                                          */
            + 1 + (max_dbs_in_event_mts * (1 + name_len)) + 3 + 1 + 32 * 3
            + 1 + 60/*
                                                                  * logType ,
                                                                  * user_len ,
                                                                  * user ,
                                                                  * host_len ,
                                                                  * host
                                                                  */);
    /* query event post-headerpacket */
    public static final int q_thread_id_offset = 0;
    public static final int q_exec_time_offset = 4;
    public static final int q_db_len_offset = 8;
    public static final int q_err_code_offset = 9;
    public static final int q_status_vars_len_offset = 11;
    public static final int q_data_offset = query_header_len;
    /* these are codes, not offsets; not more than 256 values (1 byte). */
    public static final int q_flags2_code = 0;
    public static final int q_sql_mode_code = 1;
    /**
     * q_catalog_code is catalog with end zero stored; it is used only by mysql
     * 5.0.x where 0<=x<=3. we have to keep it to be able to replicate these old
     * masters.
     */
    public static final int q_catalog_code = 2;
    public static final int q_auto_increment = 3;
    public static final int q_charset_code = 4;
    public static final int q_time_zone_code = 5;
    /**
     * q_catalog_nz_code is catalog without end zero stored; it is used by mysql
     * 5.0.x where x>=4. saves one byte in every query_log_event in binlog,
     * compared to q_catalog_code. the reason we didn't simply re-use
     * q_catalog_code is that then a 5.0.3 slave of this 5.0.x (x>=4) master
     * would crash (segfault etc) because it would expect a 0 createDateTimeLong there is
     * none.
     */
    public static final int q_catalog_nz_code = 6;
    public static final int q_lc_time_names_code = 7;
    public static final int q_charset_database_code = 8;
    public static final int q_table_map_for_update_code = 9;
    public static final int q_master_data_written_code = 10;
    public static final int q_invoker = 11;
    /**
     * q_updated_db_names status variable collects of the updated databases
     * total number and their names to be propagated to the slave in order to
     * facilitate the parallel applying of the query events.
     */
    public static final int q_updated_db_names = 12;
    public static final int q_microseconds = 13;
    /**
     * a old (unused now) code for query_log_event status similar to
     * g_commit_ts.
     */
    public static final int q_commit_ts = 14;
    /**
     * a code for query_log_event status, similar to g_commit_ts2.
     */
    public static final int q_commit_ts2 = 15;
    /**
     * the master connection @@session.explicit_defaults_for_timestamp which is
     * recorded for queries, create and alter table that is defined with a
     * timestamp column, that are dependent on that feature. for pre-wl6292
     * master's the associated with this code value is zero.
     */
    public static final int q_explicit_defaults_for_timestamp = 16;
    /**
     * from mariadb 5.5.34
     */
    public static final int q_hrnow = 128;
    /* flags2 values that can be represented inside the binlog */
    public static final int option_auto_is_null = 1 << 14;
    public static final int option_not_autocommit = 1 << 19;
    public static final int option_no_foreign_key_checks = 1 << 26;
    public static final int option_relaxed_unique_checks = 1 << 27;


    @Setter
    @Getter
    protected final String schemaName;
    /**
     * The number of seconds the query took to run on the master.
     */
    // The time in seconds that the statement took to execute. Only useful for
    // inspection by the DBA
    @Setter
    @Getter
    private final long execTime;
    @Setter
    @Getter
    private final int errorCode;
    @Setter
    @Getter
    private final long sessionId;                                                            /* thread_id */
    /* using byte for query string */
    @Setter
    @Getter
    protected String query;
    @Setter
    @Getter
    protected String catalog;
    /**
     * Fixed data part:
     * <ul>
     * <li>4 bytes. The ID of the thread that issued this statement. Needed for
     * temporary tables. This is also useful for a DBA for knowing who did what
     * on the master.</li>
     * <li>4 bytes. The time in seconds that the statement took to execute. Only
     * useful for inspection by the DBA.</li>
     * <li>1 byte. The length of the name of the database which was the default
     * database createDateTimeLong the statement was executed. This name appears later, in the
     * variable data part. It is necessary for statements such as INSERT INTO t
     * VALUES(1) that don't specify the database and rely on the default
     * database previously selected by USE.</li>
     * <li>2 bytes. The error code resulting from execution of the statement on
     * the master. Error codes are defined in include/mysqld_error.h. 0 means no
     * error. How come statements with a non-zero error code can exist in the
     * binary log? This is mainly due to the use of non-transactional tables
     * within transactions. For example, if an INSERT ... SELECT fails after
     * inserting 1000 rows into a MyISAM table (for example, with a
     * duplicateByIndex-key violation), we have to write this statement to the binary
     * log, because it truly modified the MyISAM table. For transactional
     * tables, there should be no event with a non-zero error code (though it
     * can happen, for example if the connection was interrupted (Control-C)).
     * The slave checks the error code: After executing the statement itself, it
     * compares the error code it got with the error code in the event, and if
     * they are different it stops replicating (unless --slave-skip-errors was
     * used to ignore the error).</li>
     * <li>2 bytes (not present in v1, v3). The length of the status variable
     * block.</li>
     * </ul>
     * Variable part:
     * <ul>
     * <li>Zero or more status variables (not present in v1, v3). Each status
     * variable consists of one byte code identifying the variable stored,
     * followed by the value of the variable. The format of the value is
     * variable-specific, as described later.</li>
     * <li>The default database name (null-terminated).</li>
     * <li>The SQL statement. The slave knows the size of the other fields in
     * the variable part (the sizes are given in the fixed data part), so by
     * subtraction it can know the size of the statement.</li>
     * </ul>
     * Source : http://forge.mysql.com/wiki/MySQL_Internals_Binary_Log
     */
    @Setter
    @Getter
    private String user;
    @Setter
    @Getter
    private String host;
    /**
     * 'flags2' is a second set of flags (on top of those in Log_event), for
     * session variables. These are thd->options which is & against a mask
     * (OPTIONS_WRITTEN_TO_BIN_LOG).
     */
    @Setter
    @Getter
    private long flags2;
    /**
     * In connections sql_mode is 32 bits now but will be 64 bits soon
     */
    @Setter
    @Getter
    private long sql_mode;
    @Setter
    @Getter
    private long autoIncrementIncrement = -1;
    @Setter
    @Getter
    private long autoIncrementOffset = -1;
    @Setter
    @Getter
    private int clientCharsetId = -1;//clientCharsetId
    @Setter
    @Getter
    private int clientCollation = -1;
    @Setter
    @Getter
    private int serverCollation = -1;
    @Setter
    @Getter
    private String charsetName;
    @Setter
    @Getter
    private String timezone;

    public QueryLogEvent(LogHeader logHeader, LogByteBlock logByteBlock, FormatDescriptionLogEvent formatDescriptionLogEvent)
            throws IOException
    {
        super(logHeader);

        final int commonHeaderLength = formatDescriptionLogEvent.commonHeaderLength;
        final int postHeaderLength = formatDescriptionLogEvent.postHeaderLengths[logHeader.logType - 1];
        /*
         * We test if the event's length is sensible, and if so we compute
         * data_len. We cannot rely on QUERY_HEADER_LEN here as it would not be
         * format-tolerant. We use QUERY_HEADER_MINIMAL_LEN which is the same
         * for 3.23, 4.0 & 5.0.
         */
        if (logByteBlock.limit() < (commonHeaderLength + postHeaderLength))
        {
            throw new IOException("Query event length is too short.");
        }
        int dataLen = logByteBlock.limit() - (commonHeaderLength + postHeaderLength);
        logByteBlock.setIndexAsOriginIndexAndOffset(commonHeaderLength + q_thread_id_offset);

        sessionId = logByteBlock.getLittleEndian4UnsignedLongByIndex(); // Q_THREAD_ID_OFFSET
        execTime = logByteBlock.getLittleEndian4UnsignedLongByIndex(); // Q_EXEC_TIME_OFFSET

        // TODO: add a check of all *_len vars
        final int dbLen = logByteBlock.get1UnsignedIntByIndex(); // Q_DB_LEN_OFFSET
        errorCode = logByteBlock.getLittleEndian2UnsignedIntByIndex(); // Q_ERR_CODE_OFFSET

        /*
         * 5.0 format starts here. Depending on the format, we may or not have
         * affected/warnings etc The remaining post-headerPacket to be parsed has
         * length:
         */
        int statusVarsLen = 0;
        if (postHeaderLength > query_header_minimal_len)
        {
            statusVarsLen = logByteBlock.getLittleEndian2UnsignedIntByIndex(); // Q_STATUS_VARS_LEN_OFFSET
            /*
             * Check if status variable length is corrupt and will lead to very
             * wrong data. We could be even more strict and require data_len to
             * be even bigger, but this will suffice to catch most corruption
             * errors that can lead to a crash.
             */
            if (statusVarsLen > Math.min(dataLen, max_size_log_event_status))
            {
                throw new IOException("status_vars_len (" + statusVarsLen + ") > data_len (" + dataLen + ")");
            }
            dataLen -= statusVarsLen;
        }
        /*
         * We have parsed everything we know in the post headerPacket for QUERY_EVENT,
         * the rest of post headerPacket is either comes from older version MySQL or
         * dedicated to derived events (e.g. Execute_load_query...)
         */

        /* variable-part: the status vars; only in MySQL 5.0 */
        final int start = commonHeaderLength + postHeaderLength;
        final int limit = logByteBlock.limit(); /* for restore */
        final int end = start + statusVarsLen;
        logByteBlock.setIndexAsOriginIndexAndOffset(start).limit(end);
        unpackVariables(logByteBlock, end);
        logByteBlock.setIndexAsOriginIndexAndOffset(end);
        logByteBlock.limit(limit);

        /* A 2nd variable part; this is common to all versions */
        final int queryLen = dataLen - dbLen - 1;
        schemaName = logByteBlock.getFixString(dbLen + 1);
        if (clientCharsetId >= 0)
        {
            charsetName = CharSet.getJavaCharset(clientCharsetId);

            if ((charsetName != null) && (Charset.isSupported(charsetName)))
            {
                query = logByteBlock.getFixStringByIndex(queryLen, charsetName);
            } else
            {
                log.warn("unsupported character set in query log: " + "\n    ID = " + clientCharsetId + ", Charset = "
                        + CharSet.getCharset(clientCharsetId) + ", Collation = "
                        + CharSet.getCollation(clientCharsetId));

                query = logByteBlock.getFixString(queryLen);
            }
        } else
        {
            query = logByteBlock.getFixString(queryLen);
        }
    }

    private static final String findCodeName(final int code)
    {
        switch (code)
        {
            case q_flags2_code:
                return "q_flags2_code";
            case q_sql_mode_code:
                return "q_sql_mode_code";
            case q_catalog_code:
                return "q_catalog_code";
            case q_auto_increment:
                return "q_auto_increment";
            case q_charset_code:
                return "q_charset_code";
            case q_time_zone_code:
                return "q_time_zone_code";
            case q_catalog_nz_code:
                return "q_catalog_nz_code";
            case q_lc_time_names_code:
                return "q_lc_time_names_code";
            case q_charset_database_code:
                return "q_charset_database_code";
            case q_table_map_for_update_code:
                return "q_table_map_for_update_code";
            case q_master_data_written_code:
                return "q_master_data_written_code";
            case q_updated_db_names:
                return "q_updated_db_names";
            case q_microseconds:
                return "q_microseconds";
        }
        return "CODE#" + code;
    }

    private final void unpackVariables(LogByteBlock logByteBlock, final int end) throws IOException
    {
        int code = -1;
        try
        {
            while (logByteBlock.index$originIndex() < end)
            {
                switch (code = logByteBlock.get1UnsignedIntByIndex())
                {
                    case q_flags2_code:
                        flags2 = logByteBlock.getLittleEndian4UnsignedLongByIndex();
                        break;
                    case q_sql_mode_code:
                        sql_mode = logByteBlock.getLittleEndian8SignedLongByIndex(); // QQ: Fix createDateTimeLong sql_mode
                        // is ulonglong
                        break;
                    case q_catalog_nz_code:
                        catalog = logByteBlock.getString();
                        break;
                    case q_auto_increment:
                        autoIncrementIncrement = logByteBlock.getLittleEndian2UnsignedIntByIndex();
                        autoIncrementOffset = logByteBlock.getLittleEndian2UnsignedIntByIndex();
                        break;
                    case q_charset_code:
                        // Charset: 6 byte character set flag.
                        // 1-2 = character set client
                        // 3-4 = collation client
                        // 5-6 = collation server
                        clientCharsetId = logByteBlock.getLittleEndian2UnsignedIntByIndex();
                        clientCollation = logByteBlock.getLittleEndian2UnsignedIntByIndex();
                        serverCollation = logByteBlock.getLittleEndian2UnsignedIntByIndex();
                        break;
                    case q_time_zone_code:
                        timezone = logByteBlock.getString();
                        break;
                    case q_catalog_code: /* for 5.0.x where 0<=x<=3 masters */
                        final int len = logByteBlock.get1UnsignedIntByIndex();
                        catalog = logByteBlock.getFixString(len + 1);
                        break;
                    case q_lc_time_names_code:
                        // lc_time_names_number = bytes.getLittleEndian2UnsignedIntByIndex();
                        logByteBlock.forwardByIndex(2);
                        break;
                    case q_charset_database_code:
                        // charset_database_number = bytes.getLittleEndian2UnsignedIntByIndex();
                        logByteBlock.forwardByIndex(2);
                        break;
                    case q_table_map_for_update_code:
                        // table_map_for_update = bytes.getLittleEndian8UnsignedLong();
                        logByteBlock.forwardByIndex(8);
                        break;
                    case q_master_data_written_code:
                        // data_written = master_data_written =
                        // bytes.getLittleEndian4UnsignedLongByIndex();
                        logByteBlock.forwardByIndex(4);
                        break;
                    case q_invoker:
                        user = logByteBlock.getString();
                        host = logByteBlock.getString();
                        break;
                    case q_microseconds:
                        // createDateTimeLong.tv_usec= uint3korr(pos);
                        logByteBlock.forwardByIndex(3);
                        break;
                    case q_updated_db_names:
                        int mtsAccessedDbs = logByteBlock.get1UnsignedIntByIndex();
                        /**
                         * Notice, the following check is positive also in case
                         * of the master's MAX_DBS_IN_EVENT_MTS > the slave's
                         * one and the event contains e.g the master's
                         * MAX_DBS_IN_EVENT_MTS db:s.
                         */
                        if (mtsAccessedDbs > max_dbs_in_event_mts)
                        {
                            mtsAccessedDbs = over_max_dbs_in_event_mts;
                            break;
                        }
                        String mtsAccessedDbNames[] = new String[mtsAccessedDbs];
                        for (int i = 0; i < mtsAccessedDbs && logByteBlock.index$originIndex() < end; i++)
                        {
                            int length = end - logByteBlock.index$originIndex();
                            mtsAccessedDbNames[i] = logByteBlock.getFixString(length < name_len ? length : name_len);
                        }
                        break;
                    case q_explicit_defaults_for_timestamp:
                        logByteBlock.forwardByIndex(1);
                        break;
                    case q_hrnow:
                        // int when_sec_part = bytes.getLittleEndian3UnsignedIntByIndex();
                        logByteBlock.forwardByIndex(3);
                        break;
                    default:
                        /*
                         * That's why you must write status vars in growing
                         * order of code
                         */
                        if (log.isDebugEnabled())
                            log.debug("Query_log_event has unknown status vars (first has code: "
                                    + code + "), skipping the rest of them");
                        break; // Break loop
                }
            }
        } catch (RuntimeException e)
        {
            throw new IOException("Read " + findCodeName(code) + " error: " + e.getMessage(), e);
        }
    }

}
